package com.yfqy.app.concurrency;


import jakarta.annotation.Resource;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class StockService {

    @Resource(name = "singleRedisClient")
    private RedissonClient redissonClient;

    // 建议使用这个更健壮的Lua脚本，增加了对num的nil检查
    // 原子性扣除库存
    private static final String MULTI_STOCK_LUA =
            "for i = 1, #KEYS do\n" +
                    "    local stock = redis.call('get', KEYS[i])\n" +
                    "    if not stock then\n" +
                    "        return 0\n" +
                    "    end\n" +
                    "    local num = tonumber(ARGV[i])\n" +
                    "    if not num then\n" + // 增加对num的nil检查
                    "        return 0\n" +
                    "    end\n" +
                    "    if tonumber(stock) < num then\n" +
                    "        return 0\n" +
                    "    end\n" +
                    "end\n" +
                    "for i = 1, #KEYS do\n" +
                    "    redis.call('decrby', KEYS[i], ARGV[i])\n" +
                    "end\n" +
                    "return 1";

    // 原子性回滚库存
    private static final String ROLLBACK_STOCK_LUA =
            "for i = 1, #KEYS do\n" +
                    "    local stock = redis.call('get', KEYS[i])\n" +
                    "    if not stock then\n" +
                    "        redis.call('set', KEYS[i], ARGV[i])\n" +
                    "    else\n" +
                    "        redis.call('incrby', KEYS[i], ARGV[i])\n" +
                    "    end\n" +
                    "end\n" +
                    "return 1";

    /**
     * Redisson方式多SKU原子扣减库存
     */
    public boolean deductMultiStock(Map<Long, Integer> skuIdToNum, String prefix) {
        List<Object> keys = new ArrayList<>();
        List<Object> values = new ArrayList<>();
        skuIdToNum.forEach((key, value) -> {
            keys.add(prefix + key);
            values.add(value);
        });

        // 获取一个使用 StringCodec 的 RScript 对象
        RScript script = redissonClient.getScript(StringCodec.INSTANCE);

        Object result = script.eval(
                RScript.Mode.READ_WRITE,
                MULTI_STOCK_LUA,
                RScript.ReturnType.INTEGER,
                keys,
                values.toArray()
        );
        return result != null && ((Long) result) == 1L;
    }

    /**
     * Redisson方式多SKU原子扣减库存（重试版）
     *
     * @param skuIdToNum
     * @param maxRetries
     * @param intervalTime
     * @return
     */
    public boolean deductMultiStockWithRetry(Map<Long, Integer> skuIdToNum, String prefix, int maxRetries, long intervalTime) {
        int retryCount = 0;
        while (retryCount < maxRetries) {
            try {
                return deductMultiStock(skuIdToNum, prefix);
            } catch (Exception e) {
                retryCount++;
                if (retryCount >= maxRetries) {
                    throw new RuntimeException("扣减库存失败，重试次数耗尽", e);
                }
                try {
                    Thread.sleep(intervalTime);
                } catch (InterruptedException e1) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("重试被中断", e1);
                }
            }
        }
        return false;
    }

    /**
     * Redisson方式多SKU原子回滚库存
     * @param skuIdToNum 需要回滚的SKU及数量映射
     * @return 是否回滚成功
     */
    public boolean rollBackedMultiStock(Map<Long, Integer> skuIdToNum, String prefix) {
        List<Object> keys = new ArrayList<>();
        List<Object> values = new ArrayList<>();

        skuIdToNum.forEach((key, value) -> {
            keys.add(prefix + key);
            values.add(String.valueOf(value));
        });

        RScript script = redissonClient.getScript(StringCodec.INSTANCE);

        Object result = script.eval(
                RScript.Mode.READ_WRITE,
                ROLLBACK_STOCK_LUA,
                RScript.ReturnType.INTEGER,
                keys,
                values.toArray()
        );

        return result != null && ((Long) result) == 1L;
    }
}
